﻿using System;

namespace MLSharp.Classification
{
	/// <summary>
	/// A simple classifier that will guess the value of one attribute
	/// based on the value of another attribute of the same type.
	/// </summary>
	public class SimpleAttributeClassifier : IClassifier
	{
		#region Private Fields

		/// <summary>
		/// The index of the attribute that predicts the class.
		/// </summary>
		private readonly int mPredictingAttributeIndex;

		#endregion

		#region Public Constructors

		/// <summary>
		/// Instantiates the classifier.
		/// </summary>
		/// <param name="attributes">The attributes that instances to be classified will contain.</param>
		/// <param name="predictingAttributeIndex">The index of the attribute that predicts the value of the target.</param>
		/// <param name="targetAttributeIndex">The index of the attribute to predict.</param>
		public SimpleAttributeClassifier(DataAttribute[] attributes, int predictingAttributeIndex, int targetAttributeIndex)
		{
			//Make sure the predicting attribute and the target attribute are of the same type
			DataAttribute predictingAttribute = attributes[predictingAttributeIndex];
			DataAttribute targetAttribute = attributes[targetAttributeIndex];

			if (predictingAttribute.Type != targetAttribute.Type)
			{
				throw (new InvalidOperationException(
					string.Format("Predicting attribute type ({0}) and target attribute type ({1}) differ.",
					              predictingAttribute.Type, targetAttribute.Type)));
			}

			//If they're Set-valued, make sure they have equal allowed types
			if (predictingAttribute.Type == AttributeType.Set)
			{
				if (!Array.TrueForAll(predictingAttribute.PossibleValues, a => Array.IndexOf(targetAttribute.PossibleValues, a) >= 0)
					|| !Array.TrueForAll(targetAttribute.PossibleValues, a => Array.IndexOf(predictingAttribute.PossibleValues, a) >= 0))
				{
					throw (new InvalidOperationException(
						string.Format("Predicting attribute type ({0}) and target attribute type ({1}) can take different set values.",
									  string.Join(",", predictingAttribute.PossibleValues), 
									  string.Join(",", targetAttribute.PossibleValues))));
				}
			}

			mPredictingAttributeIndex = predictingAttributeIndex;
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Classifies all the instances in the specified data set.
		/// </summary>
		/// <param name="dataSet">The data set to classify.</param>
		/// <returns>The results of classifying each instance.</returns>
		public ClassificationResult[] Classify(IDataSet dataSet)
		{
			ClassificationResult[] results = new ClassificationResult[dataSet.Instances.Count];

			for (int i=0; i < dataSet.Instances.Count; i++)
			{
				string prediction = dataSet.Instances[i].GetStringValue(mPredictingAttributeIndex);

				ClassificationResult result = new ClassificationResult(prediction, dataSet.Instances[i].ClassValue)
				                              	{
				                              		Confidence = double.NaN,
				                              		ID = dataSet.Instances[i].Label
				                              	};

				results[i] = result;
			}

			return results;
		}

		#endregion
	}
}
